package com.byapp.fast.modules.contract.service;

import com.baomidou.mybatisplus.mapper.EntityWrapper;
import com.byapp.fast.common.converter.AddPlayDTOConverter;
import com.byapp.fast.common.converter.ChangeBetOddsDTOConverter;
import com.byapp.fast.common.converter.CreateContractTxDTOConverter;
import com.byapp.fast.common.enums.ItemStatusEnum;
import com.byapp.fast.common.enums.OrderStatusEnum;
import com.byapp.fast.common.enums.PayStatusEnum;
import com.byapp.fast.common.enums.ResultEnum;
import com.byapp.fast.common.exception.BYException;
import com.byapp.fast.common.utils.JsonUtils;
import com.byapp.fast.common.utils.PlayIdUtils;
import com.byapp.fast.common.utils.SignUtils;
import com.byapp.fast.common.utils.UUIDGenerator;
import com.byapp.fast.modules.contract.biz.BusinessBiz;
import com.byapp.fast.modules.contract.biz.RetBiz;
import com.byapp.fast.modules.contract.dto.*;
import com.byapp.fast.modules.match.entity.MatchOddsEntity;
import com.byapp.fast.modules.match.entity.MatchRefEntity;
import com.byapp.fast.modules.match.service.MatchService;
import com.byapp.fast.modules.order.entity.ItemContentEntity;
import com.byapp.fast.modules.order.entity.OrderEntity;
import com.byapp.fast.modules.order.entity.OrderItemEntity;
import com.byapp.fast.modules.order.entity.OrderItemExtEntity;
import com.byapp.fast.modules.order.service.ItemContentService;
import com.byapp.fast.modules.order.service.OrderItemExtService;
import com.byapp.fast.modules.order.service.OrderItemService;
import com.byapp.fast.modules.order.service.OrderService;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.util.StopWatch;
import org.springframework.web.client.RestTemplate;

import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;

@Service("contractService")
@Slf4j
public class ContractService {

    @Autowired
    public RestTemplate restTemplate;

    @Autowired
    private MatchService matchService;

    @Autowired
    private OrderService orderService;

    @Autowired
    private OrderItemService orderItemService;

    @Autowired
    private OrderItemExtService orderItemExtService;

    @Autowired
    private ItemContentService itemContentService;

    @Autowired
    private BusinessBiz businessBiz;

    @Value("${contract.addPlay}")
    private String addPlay;

    @Value("${contract.changeBetOdds}")
    private String changeBetOdds;

    @Value("${contract.createContractTx}")
    private String createContractTx;

    @Value("${contract.cancelOrder}")
    private String cancelOrder;

    @Value("${contract.queryOrder}")
    private String queryOrder;

    /**
     * 合约更新盘口/赔率
     */
    public void callChangeBetOdds(MatchOddsEntity matchOddsEntity) {
        ChangeBetOddsDTO changeBetOddsDTO = ChangeBetOddsDTOConverter.create(matchOddsEntity);
        log.info("【合约更新盘口/赔率】接口调用开始，请求参数：url={}，changeBetOddsDTO={}", changeBetOdds, JsonUtils.toJson(changeBetOddsDTO));
        RetBiz retBiz = restTemplate.postForObject(changeBetOdds, changeBetOddsDTO, RetBiz.class);

        if (StringUtils.equals(retBiz.getStatus().toString(), "0")) {
            log.error("【合约更新盘口/赔率】接口调用失败，原因={}", retBiz.getError());
            throw new BYException(retBiz.getStatus(), retBiz.getError());
        }

        log.info("【合约更新盘口/赔率】接口调用成功，retBiz={}", JsonUtils.toJson(retBiz));
    }

    /**
     * 合约添加玩法
     */
    public void callAddPlay(MatchOddsEntity matchOddsEntity, MatchRefEntity matchRefEntity) {
        AddPlayDTO addPlayDTO = AddPlayDTOConverter.create(matchOddsEntity, matchRefEntity);
        log.info("【合约添加玩法】接口调用开始，请求参数：url={}，addPlayDTO={}", addPlay, JsonUtils.toJson(addPlayDTO));
        RetBiz retBiz = restTemplate.postForObject(addPlay, addPlayDTO, RetBiz.class);

        if (StringUtils.equals(retBiz.getStatus().toString(), "0")) {
            log.error("【合约添加玩法】接口调用失败，原因={}", retBiz.getError());
            throw new BYException(retBiz.getStatus(), retBiz.getError());
        }

        log.info("【合约添加玩法】接口调用成功，retBiz={}", JsonUtils.toJson(retBiz));
    }

    /**
     * 合约取消订单
     */
    public void callCancelOrder(CancelOrderDTO cancelOrderDTO) {
        RetBiz retBiz = restTemplate.postForObject(cancelOrder, cancelOrderDTO, RetBiz.class);
        log.info("【取消订单】接口调用开始，请求参数：url={}，cancelOrderDTO={}", changeBetOdds, JsonUtils.toJson(cancelOrderDTO));

        if (StringUtils.equals(retBiz.getStatus().toString(), "0")) {
            log.error("【取消订单】接口调用失败，原因={}", retBiz.getError());
            throw new BYException(retBiz.getStatus(), retBiz.getError());
        }

        log.info("【取消订单】接口调用成功，retBiz={}", JsonUtils.toJson(retBiz));
    }

    /**
     * 合约创建订单
     */
    public RetBiz callCreateContractTx(List<OrderItemEntity> list) {
        List<CreateContractTxDTO> createContractTxList = CreateContractTxDTOConverter.mapToTarget(list);
        RetBiz retBiz = restTemplate.postForObject(createContractTx, createContractTxList.get(0), RetBiz.class);
        log.info("【合约创建订单】接口调用开始，请求参数：url={}，createContractTxList={}", createContractTx, JsonUtils.toJson(createContractTxList));

        if (StringUtils.equals(retBiz.getStatus().toString(), "0")) {
            log.error("【合约创建订单】接口调用失败，原因={}", retBiz.getError());
            throw new BYException(retBiz.getStatus(), retBiz.getError());
        }

        log.info("【合约创建订单】接口调用成功，retBiz={}", JsonUtils.toJson(retBiz));
        return retBiz;
    }

    /**
     * 区块回调处理
     * 返回订单状态：10 成功、-5 失败
     */
    public Integer callBackEth(CallBackEthDTO callBackEthDTO) {
        StopWatch stopWatch = new StopWatch("callBackEth");

        stopWatch.start("校验TXHASH是否存在");
        // 获取请求参数
        CallBackBetDTO callBackBetDTO = callBackEthDTO.getCallBackBetDTO();
        String txHash = callBackEthDTO.getTxHash();
        String matchId = callBackBetDTO.getLid();
        String handicap = callBackBetDTO.getHandicap();
        String betItem = callBackBetDTO.getBetItem();
        String odds = callBackBetDTO.getBetOdds();
        String oddsId = callBackBetDTO.getPlayId();
        String playId = callBackBetDTO.getPlayCode();

        // 判断hash是否存在
        log.info("【判断HASH是否存在】txHash={}", txHash);
        OrderItemEntity orderItemEntity = orderItemService.selectOne(new EntityWrapper<OrderItemEntity>().eq("TXHASH", txHash));
        stopWatch.stop();

        if (orderItemEntity == null) {

            // 二次确认订单支付情况
            Map<String, Object> params = new HashMap<>();
            params.put("dappid", businessBiz.getDappid());
            params.put("txhash", txHash);
            params.put("nonce", UUIDGenerator.getUUID());
            String status = confirmOrderAgain(params);
            if (!StringUtils.equalsIgnoreCase(status, "SUCCESS")) {
                // 判断赛事是否开启
                stopWatch.start("校验赛事是否开始");
                boolean matchStartedFlag = matchService.isMatchStarted(matchId);
                if (matchStartedFlag) {
                    log.error("【赛事竞猜】赛事已开始，callBackEthDTO={}", JsonUtils.toJson(callBackEthDTO));
                    return ItemStatusEnum.ITEM_ERROR_ING.getCode();
                }
                stopWatch.stop();

                stopWatch.start("调用生成订单接口");
                // hash不存在，则调用生成订单接口
                log.info("【判断HASH是否存在】txHash不存在，调用生成订单服务，txHash={}", txHash);
                orderService.createEthOrder(callBackEthDTO);
                stopWatch.stop();
            }
        }

        stopWatch.start("校验赛事是否开盘");
        // 校验赛事是否开盘
        boolean matchBetEnabled = matchService.isMatchBetEnabled(matchId);
        if (!matchBetEnabled) {
            log.error("【区块回调】赛事已封盘，callBackEthDTO={}", JsonUtils.toJson(callBackEthDTO));
            return ItemStatusEnum.ITEM_ERROR_ING.getCode();
        }
        stopWatch.stop();

        stopWatch.start("校验玩法是否开盘");
        // 校验玩法是否开盘
        String[] plays = PlayIdUtils.getPlayItems(playId);
        Map<String, Object> params = new HashMap<>();
        params.put("MATCH_ID", matchId);
        params.put("ODDS_PHASE", 2);
        params.put("BET_TYPE", plays[0]);
        params.put("MATCH_PHASE", plays[1]);
        params.put("STATUS", 0);
        params.put("HANDICAP_VALUE", handicap);

        boolean playBetEnabled = matchService.isPlayBetEnabled(params);
        if (!playBetEnabled) {
            log.error("【区块回调】玩法已封盘，callBackEthDTO={}", JsonUtils.toJson(callBackEthDTO));
            return ItemStatusEnum.ITEM_ERROR_ING.getCode();
        }
        stopWatch.stop();

        stopWatch.start("校验赔率/盘口变化");
        // 校验赔率/盘口变化
        params.clear();
        params.put("MATCH_ID", matchId);
        params.put("HANDICAP_VALUE", handicap);
        params.put("CODES", betItem);
        params.put("ODDS", odds);
        params.put("ODDS_PHASE", 2);
        params.put("STATUS", 0);
        params.put("PLAY", plays[0]);
        params.put("MATCH_PHASE", plays[1]);
        boolean originOddsAndHandicap = matchService.isOriginOddsAndHandicap(params);
        if (!originOddsAndHandicap) {
            log.error("【区块回调】赔率/盘口发生变化，callBackEthDTO={}", JsonUtils.toJson(callBackEthDTO));
            return ItemStatusEnum.ITEM_ERROR_ING.getCode();
        }
        stopWatch.stop();

        log.info("【执行日志】{}", stopWatch.prettyPrint());
        return ItemStatusEnum.ITEM_SUCCESS.getCode();
    }


    /**
     * 支付回调处理
     */
    public void callBackPay(CallBackPayDTO callBackPayDTO) {
        StopWatch stopWatch = new StopWatch("callBackPay");

        stopWatch.start("更新订单数据");

        // 获取请求参数
        CallBackReturnDTO returnMsg = callBackPayDTO.getReturnMsg();
        List<CallBackRecordDTO> records = returnMsg.getRecords();

        Optional<CallBackRecordDTO> first = records.stream()
                .filter(record -> StringUtils.equals(record.getTxtype(), "2"))
                .findFirst();
        boolean present = first.isPresent();
        if (!present) {
            log.error("【更新订单】更新订单失败，参数异常用户ID不存在，CallBackReturnDTO={}", returnMsg);
            throw new BYException(ResultEnum.ILLEGAL_PARAM);
        }
        String userId = first.get().getTxaddress();
        String orderId = returnMsg.getOutTradeNum();
        String txHash = returnMsg.getTxhash();

        // 更新订单数据
        OrderEntity orderEntity = orderService.selectById(orderId);
        if (orderEntity == null) {
            log.error("【更新订单】更新订单失败，订单不存在，orderId={}", orderId);
            throw new BYException(ResultEnum.ILLEGAL_PARAM);
        }
        orderEntity.setBuyUser(userId);
        orderEntity.setPayStatus(PayStatusEnum.PAY_SUCCESS.getCode());
        orderEntity.setOrderStatus(OrderStatusEnum.ORDER_SUCCESS.getCode());
        log.info("【更新订单】更新订单数据，orderEntity={}", JsonUtils.toJson(orderEntity));
        orderService.updateById(orderEntity);
        stopWatch.stop();

        stopWatch.start("更新投注单数据");
        // 更新投注单数据
        OrderItemEntity orderItemEntity = orderItemService.selectOne(new EntityWrapper<OrderItemEntity>().eq("ORDER_ID", orderId));
        if (orderItemEntity == null) {
            log.error("【更新订单】更新投注单失败，投注单不存在，orderId={}", orderId);
            throw new BYException(ResultEnum.ILLEGAL_PARAM);
        }
        String itemId = orderItemEntity.getItemId();
        orderItemEntity.setUserId(userId);
        orderItemEntity.setTxhash(txHash);
        orderItemEntity.setItemStatus(ItemStatusEnum.ITEM_INIT.getCode());
        log.info("【更新订单】更新投注单数据，orderItemEntity={}", JsonUtils.toJson(orderItemEntity));
        orderItemService.updateById(orderItemEntity);
        stopWatch.stop();

        stopWatch.start("更新投注单扩展数据");
        // 更新投注单扩展数据
        OrderItemExtEntity orderItemExtEntity = orderItemExtService.selectOne(new EntityWrapper<OrderItemExtEntity>().eq("ITEM_ID", itemId));
        if (orderItemExtEntity == null) {
            log.error("【更新订单】更新投注单扩展失败，投注单扩展不存在，itemId={}", itemId);
            throw new BYException(ResultEnum.ILLEGAL_PARAM);
        }
        orderItemExtEntity.setUserId(userId);
        log.info("【更新订单】更新投注单扩展数据，orderItemExtEntity={}", JsonUtils.toJson(orderItemExtEntity));
        orderItemExtService.updateById(orderItemExtEntity);
        stopWatch.stop();

        stopWatch.start("更新投注单详情数据");
        // 更新投注单详情数据
        ItemContentEntity itemContentEntity = itemContentService.selectOne(new EntityWrapper<ItemContentEntity>().eq("ITEM_ID", itemId));
        if (itemContentEntity == null) {
            log.error("【更新订单】更新投注单详情失败，投注单详情不存在，itemId={}", itemId);
            throw new BYException(ResultEnum.ILLEGAL_PARAM);
        }
        itemContentEntity.setUserId(userId);
        log.info("【更新订单】更新投注单详情数据，itemContentEntity={}", JsonUtils.toJson(itemContentEntity));
        itemContentService.updateById(itemContentEntity);
        stopWatch.stop();

        log.info("【执行日志】{}", stopWatch.prettyPrint());
    }


    public String confirmOrderAgain(Map<String, Object> map) {
        String signTemp = SignUtils.generateURL(map);
        String sign = SignUtils.encodeMD5(signTemp).toUpperCase();
        map.put("sign", sign);
        CallBackPayDTO callBackPayDTO = restTemplate.postForObject(queryOrder, JsonUtils.toNormalJson(map), CallBackPayDTO.class);
        if (callBackPayDTO == null || !StringUtils.equalsIgnoreCase(callBackPayDTO.getReturnCode(), "SUCCESS")) {
            return "FAILURE";
        }
        log.info("【手动支付回调】callBackPay={}", JsonUtils.toJson(callBackPayDTO));
        callBackPay(callBackPayDTO);
        return "SUCCESS";
    }

}
